package com.shujia.scala

import org.junit.{After, Before, Test}

import scala.collection.immutable
import scala.io.{BufferedSource, Source}

class Demo22StuExercise {
  // 定义三个样例类对象分别接收三份数据
  case class Students(id: String, name: String, age: Int, gender: String, clazz: String)

  case class Score(id: String, subjectId: String, score: Int)

  case class Subject(subjectId: String, subjectName: String, subjectScore: Int)

  var bs01: BufferedSource = _
  var bs02: BufferedSource = _
  var bs03: BufferedSource = _
  var stu: List[Students] = _
  var score: List[Score] = _
  var subject: List[Subject] = _
  var sumScore: Map[String, Int] = _
  var stuNameAndClazzMap: Map[String, (String, String)] = _
  var subjectMap: Map[String, (String, Int)] = _

  @Before
  def init(): Unit = {
    // 加载学生、成绩、科目数据
    bs01 = Source.fromFile("data/stu/students.txt")
    bs02 = Source.fromFile("data/stu/score.txt")
    bs03 = Source.fromFile("data/stu/subject.txt")

    // 迭代器每次只能迭代一次，相当于是一次性的，所以可以将其转成List进行多次使用
    // toList要考虑数据量的问题
    stu = bs01.getLines()
      .map(line => {
        val splits: Array[String] = line.split(",")
        Students(splits(0), splits(1), splits(2).toInt, splits(3), splits(4))
      }).toList

    score = bs02.getLines()
      .map(line => {
        val splits: Array[String] = line.split(",")
        Score(splits(0), splits(1), splits(2).toInt)
      }).toList

    subject = bs03.getLines()
      .map(line => {
        val splits: Array[String] = line.split(",")
        Subject(splits(0), splits(1), splits(2).toInt)
      }).toList


    sumScore = score
      .groupBy(_.id)
      .map(t2 => {
        val id: String = t2._1
        val sumScore: Int = t2._2.map(_.score).sum
        (id, sumScore)
      })

    // 以学生id作为key，姓名和班级作为value构建Map
    stuNameAndClazzMap = stu
      .map(s => (s.id, (s.name, s.clazz)))
      .toMap

    // 以科目id作为key，科目名和科目总分作为value构建Map
    subjectMap = subject.map(s => (s.subjectId, (s.subjectName, s.subjectScore))).toMap

  }

  @Test
  def question2_1(): Unit = {
    // 1、统计性别人数 [性别,人数]
    stu
      // 提取用到的字段
      .map(_.gender)
      .groupBy(gender => gender)
      .map(t2 => {
        s"${t2._1},${t2._2.size}"
      })
      .foreach(println)
  }

  @Test
  def question2_2(): Unit = {
    // 2、统计每个学生平均分 [学号,平均分]
    score
      .groupBy(_.id)
      .map(t2 => {
        val id: String = t2._1
        val sumScore: Int = t2._2.map(_.score).sum
        s"$id,${sumScore / t2._2.size.toDouble}"
      })
      .foreach(println)
  }

  @Test
  def question2_3(): Unit = {
    // 3、统计每个学生的总分 [学号,学生姓名,学生年龄,总分]
    // 基于学生数据构建Map用于关联，以学号作为Key，以姓名和年龄一起作为Value
    val stuMap: Map[String, (String, Int)] = stu.map(s => (s.id, (s.name, s.age))).toMap

    score
      .groupBy(_.id) // 按学生id进行分组
      .map(t2 => {
        val id: String = t2._1
        // 提取学生的成绩 最后计算总分
        val sumScore: Int = t2._2.map(_.score).sum
        // 通过id提取学生的name和age
        val nameAndAge: (String, Int) = stuMap.getOrElse(id, ("", 0))
        s"$id,${nameAndAge._1},${nameAndAge._2},$sumScore"
      }).foreach(println)
  }

  def printStuInfoAndSubWithIds(idList:List[String]):Unit = {
    // 遍历分数的数据
    score
      // 基于找到的id列表进行过滤
      .filter(s => idList.contains(s.id))
      .map(s => {
        // 通过id及科目id去另外两份数据的Map中获取所需数据
        val id: String = s.id
        val nameAndClazz: (String, String) = stuNameAndClazzMap.getOrElse(id, ("", ""))
        val subjectName: String = subjectMap.getOrElse(s.subjectId, ("", 0))._1
        s"$id,${nameAndClazz._1},${nameAndClazz._2},$subjectName,${s.score}"
      })
      .foreach(println)
  }

  @Test
  def question3_1(): Unit = {
    // 统计年级排名前十学生各科的分数 [学号, 姓名，班级，科目，分数]
    /*
    (1500100929,630)
(1500100308,628)
(1500100080,628)
(1500100873,612)
(1500100418,611)
(1500100258,604)
(1500100875,595)
(1500100930,589)
(1500100596,587)
(1500100834,586)
     */
    val idList: List[String] = sumScore
      .toList
      .sortBy(-_._2)
      .map(_._1)
      .take(10)

    printStuInfoAndSubWithIds(idList)
  }

  @Test
  def question3_2(): Unit = {
    // 统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
    // 计算年级总分的平均分
    val avgScore: Double = sumScore.values.sum / sumScore.size.toDouble
    // 遍历学生总分数据 过滤出 总分大于平均分的学生成绩数据
    sumScore
      .filter(_._2 > avgScore)
      .map(t2 => {
        // 通过学生id从学生的姓名班级Map中提取姓名和班级
        val nameAndClazz: (String, String) = stuNameAndClazzMap.getOrElse(t2._1, ("", ""))
        s"${t2._1},${nameAndClazz._1},${nameAndClazz._2},${t2._2}"
      })
      .foreach(println)
  }

  @Test
  def question3_3(): Unit = {
    // 统计每科都及格的学生 [学号，姓名，班级，科目，分数]
    // 关联分数和科目的数据，随后判断科目成绩是否及格，将不及格的成绩过滤掉
    val scoreCntMap: Map[String, Int] = score
      .groupBy(_.id)
      .map(kv => (kv._1, kv._2.size))

    val idList: List[String] = score
      .filter(s => {
        val subjectScore: Int = subjectMap.getOrElse(s.subjectId, ("", 999))._2
        s.score / subjectScore.toDouble >= 0.6
      })
      // 统计每个学生及格科目的数量
      .groupBy(_.id)
      .map(t2 => {
        (t2._1, t2._2.size)
      }).filter(kv => {
        kv._2 == scoreCntMap.getOrElse(kv._1, 0)
      })
      .keys
      .toList

    printStuInfoAndSubWithIds(idList)

  }

  @Test
  def question3_4():Unit = {
    // 统计每个班级的前三名 [学号，姓名，班级，分数]
    sumScore
    // 通过stuNameAndClazzMap获取学生的姓名和班级
    .map(t2=>{
      val id: String = t2._1
      val nameAndClazz: (String, String) = stuNameAndClazzMap.getOrElse(id, ("", ""))
      (id,nameAndClazz._1,nameAndClazz._2,t2._2)
    })
      .groupBy(_._3)
      .flatMap(t2=>{
        t2._2.toList.sortBy(-_._4).take(3)
      })
      .map(t=>s"${t._1},${t._2},${t._3},${t._4}")
      .foreach(println)
  }

  @Test
  def question3_5():Unit = {
    // 统计偏科最严重的前100名学生 [学号，姓名，班级，科目，分数]
    // 偏科：通过方差衡量
    val idList: List[String] = score
      // 基于每一个人的6门科目成绩计算出一个方差
      .groupBy(_.id)
      .map(t2 => {
        val id: String = t2._1
        val scoreList: List[Score] = t2._2
        // 计算6门科目的平均成绩
        // 将每门科目的成绩都转换成百分制
        val newScoreList: List[Double] = scoreList.map(s => {
          // 通过科目id获取科目总分
          val subjectScore: Int = subjectMap.getOrElse(s.subjectId, ("", 999))._2
          s.score * 100.0 / subjectScore
        })
        val avgScore: Double = newScoreList.sum / newScoreList.size
        val fc: Double = newScoreList.map(s => Math.pow(s - avgScore, 2)).sum / newScoreList.size
        (id, fc)
      })
      .toList
      .sortBy(-_._2)
      .map(_._1)
      .take(100)

    printStuInfoAndSubWithIds(idList)


  }

  @After
  def close(): Unit = {
    bs01.close()
    bs02.close()
  }

}

